defmodule GatewayRouter do
    use Plug.Router     # require Plug.Router & Feature.__using__(option: )

    alias ShareGroup.Repo
    alias ShareGroup.User
    alias ShareGroup.Email
    alias ShareGroup.Mailer
    require Path
    require Logger

    @auth_session_field "visitor"
    @validate_code_session "vode"
    @host_email "15521283615@163.com"

    @validate_sercret_key_base "v4YLiacXAlByOoRBAVGA+udb8Jkf1eaRAqydc5wp+boZSx+0afpoLBpi74CwFsq/Q/qN/X/2YrDTWRC4aVef+Q=="
    @validate_encrypted_cookie_salt "0okmj8iujn7yh"
    @validate_encrypted_signed_cookie_salt "5rfhu56tfg4f32"

    plug Plug.Static,
            at: "/",
            from: Path.expand("../../"),#指向最上级目录
            only: ~w(src dist public)
                
    plug :match

    @doc """
    因为在conn中secret_key_base只能存在一个值，所以一个conn顶多只能用一次Plug.SEssion。不方便, 想办法扩展。
    guardian实现了大概类似的功能，看看是否应该用guardian
    """

    plug :put_secret_key_base
    plug Plug.Session, store: :cookie, 
                   key: @auth_session_field,
                   encryption_salt: "cookie store encryption salt",
                   signing_salt: "cookie store signing salt",
                   key_length: 64,
                   log: :debug

    plug :fetch_session
    plug Plug.Parsers, parsers: [:json], #解析json请求
                pass: ["apllication/json"],
                json_decoder: Poison

    plug Guardian.Plug.VerifySession, key: @auth_session_field
    plug Guardian.Plug.LoadResource, key: @auth_session_field

    plug :dispatch

    @doc """
    登陆路由，确认用户账户和密码
    """
    post "/login" do

        msgusername = conn.body_params["username"]  #这里的username指的是telephone 或者 mail
        msgpassword = conn.body_params["password"]

        Logger.info msgusername <> " with " <> msgpassword <> " post to login!"

        IO.inspect conn
        case findandconfirm(msgusername, msgpassword) do
            nil ->
                Logger.info "can't find #{msgusername} with #{msgpassword}"
                conn
                    |> put_resp_content_type("text/plain")
                    |> send_resp(404, "fail to login")
            user ->
                Logger.info "find #{msgusername} with #{msgpassword}"
                conn 
                |> Guardian.Plug.sign_in(user, :access, %{key: @auth_session_field })
                |> put_resp_content_type("text/plain")
                |> send_resp(200, "login suessfully")
        end

    end

    @doc """
    登出路由
    """
    post "/logout" do
        conn
        |> Guardian.Plug.sign_out(String.to_atom(@auth_session_field))
        |> put_resp_content_type("text/plain")
        |> send_resp(200, "logout suessfully")
    end

    @doc """
    注册模块，根据输入的手机号，邮箱号，用户名和密码判断对应的动作
    """
    post "/register" do
        
        msgtelephone = conn.body_params["telephone"]
        msgmail = conn.body_params["mail"]
        msgusername = conn.body_params["username"]
        msgpassword = conn.body_params["password"]
        
        user = %User{}

        changeset = User.changeset(user, %{telephone: msgtelephone, mail: msgmail, username: msgusername, password: msgpassword})

        case changeset.valid? do
            true ->
                hashpawword = User.generateHash(msgtelephone, msgpassword)
                Logger.info "uesr hash password == " <> hashpawword
                changeset = changeset
                            |> Ecto.Changeset.put_change(:password, hashpawword)
                case Repo.insert(changeset) do
                    {:ok, user} ->
                        # Email.welcome_email([from: @host_email, to: user.mail]) |> Mailer.deliver_now

                        conn
                        |> put_resp_content_type("text/plain")
                        |> send_resp(200, "#{user.username} register successfully")

                    {:error, msg} ->
                        errormsg = User.getErrorMsg(msg.errors, %{})
                        {:ok, errormsgjson} = Poison.encode(errormsg)
                        conn 
                        |> put_resp_content_type("text/plain")
                        |> send_resp(400, errormsgjson)
                end
            false ->
                errormsg = User.getErrorMsg(changeset.errors, %{})
                {:ok, errormsgjson} = Poison.encode(errormsg)
                conn 
                    |> put_resp_content_type("text/plain")
                    |> send_resp(400, errormsgjson)
        end

    end

    @doc """
    根据在cookies中的验证码和邮箱，判断当前修改密码是否有效
    """
    post "/changepsd" do

        case conn.cookies[@validate_code_session] do
            nil ->
                conn
                |> put_resp_content_type("text/plain")
                |> send_resp(400, "invalid code")

            encrypted ->
                validatecode = conn.body_params["validatecode"]
                msg = encrypted |> validate_decrypt
                {codeincookies, mail} = String.split_at(msg, 8)

                if codeincookies == validatecode do
                    newpassword = conn.body_params["newpassword"]
                    user = %User{}
                    user = user |> Map.put(:mail, mail)
                    case User.findUser(user, :mail) do
                        nil ->
                            conn
                            |> delete_resp_cookie(@validate_code_session)
                            |> put_resp_content_type("text/plain")
                            |> send_resp(400, "invalid code")
                        user ->
                            hash = User.generateHash(user.telephone, newpassword)
                            changeset = User.changeset(user, %{password: hash})
                            case Repo.update(changeset) do
                                {:ok, new} ->
                                    conn
                                    |> delete_resp_cookie(@validate_code_session)
                                    |> put_resp_content_type("text/plain")
                                    |> send_resp(200, "change password successfully")
                                {:error, _} ->
                                    conn
                                    |> delete_resp_cookie(@validate_code_session)
                                    |> put_resp_content_type("text/plain")
                                    |> send_resp(400, "invalid code")
                            end
                    end

                else
                    conn
                    |> delete_resp_cookie(@validate_code_session)
                    |> put_resp_content_type("text/plain")
                    |> send_resp(400, "invalid code")

                end
                
        end
    end

    @doc """
    获取验证码，用于验证忘记密码,将验证码以及对应的邮箱经过加密后加入cookies中
    """
    post "/getValidateCode" do
        msgmail = conn.body_params["email"]

        user = %User{}
        user = user |> Map.put(:mail, msgmail)
        type = User.detectUserType(user.mail)
        case User.findUser(user, type) do
            nil -> 
                conn 
                |> put_resp_content_type("text/plain")
                |> send_resp(400, "not found " <> msgmail)
            user ->
                validatecode = SecureRandom.base64(6) #生成八位的验证码

                validate = validate_encrypt(validatecode <> msgmail)
                body = "<p>您当前正在修改密码， 验证码为：#{validatecode}</p>"
                Email.validate_email([from: @host_email, to: user.mail, html_body: body]) |> Mailer.deliver_now

                conn
                |> put_resp_cookie(@validate_code_session, validate)
                |> put_resp_content_type("text/plain")
                |> send_resp(200, "already sent to #{msgmail}")
        end
    end

    get "/isUser" do

        # case Guardian.Plug.claims(conn, @session_field) do
        #     {:ok, map} -> IO.inspect map
        #     {:error, msg} -> IO.inspect msg
        # end

        case Guardian.Plug.current_resource(conn, @auth_session_field) do
            nil -> 
                conn
                |> put_resp_content_type("text/pain")
                |> send_resp(403, "unknown user")
            user ->
                conn
                |> put_resp_content_type("text/pain")
                |> send_resp(200, "#{user.username} is user")
        end
    end

    post "/club" do
        case Guardian.Plug.current_resource(conn, @auth_session_field) do
            nil -> 
                return = %{} 
                        |> Map.put("result", false)
                        |> Map.put("errormsg", "用户尚未登录")
                {:ok, json} = Poison.encode(return)
                conn
                |> put_resp_content_type("text/json")
                |> send_resp(200, json)
            user ->
                params = changeStrKey2AtomKey(conn.body_params)
                params = params |> Map.put(:username, user.username)
                {res, data} = case CentralControl.central_control(Map.put(params, :userid, user.id)) do
                                    {:ok, data} ->
                                        {:ok, data}
                                    {:error, msg} ->
                                        {:error, %{"errormsg" => msg}}
                                end
                return = %{} 
                    |> Map.put("result", res == :ok)
                    |> Map.merge(data)
                {:ok, json} = Poison.encode(return)
                conn
                |> put_resp_content_type("text/json")
                |> send_resp(200, json)
        end
    end

    post "/user" do
        idlist = conn.body_params["idlist"]
        list = User.shownamelist(idlist)
        return = %{} 
                    |> Map.put("result", true)
                    |> Map.merge(list)
        {:ok, json} = Poison.encode(return)
        conn
        |> put_resp_content_type("text/json")
        |> send_resp(200, json)
    end

    post "/upload" do
        case Guardian.Plug.current_resource(conn, @auth_session_field) do
            nil -> 
                conn
                |> put_resp_content_type("text/pain")
                |> send_resp(403, "unknown user")
            user ->
                IO.inspect conn
                conn
                |> put_resp_content_type("text/pain")
                |> send_resp(200, "#{user.username} is user")
        end
    end

    match _ do
        conn
        |> put_resp_content_type("text/plain")
        |> send_resp(404, "NOT FOUND")
    end

    def changeStrKey2AtomKey(params) do
        for {str, value} <- params, into: %{}, do: { String.to_atom(str), value}
    end


    def findandconfirm(msgusername, msgpassword) do #寻找并且验证用户


        type = User.detectUserType(msgusername)

        if type in [:telephone, :mail] do
            user = %User{}

            user
                |>Map.put(type, msgusername)
                |>User.findUser(type)
                |>User.confirmUser(msgpassword)
        else
            nil
        end
    end

    def put_secret_key_base(conn, _) do
        put_in conn.secret_key_base, "bNEipVi6NML6KQkkL5EkOmo596LNjRUJZofluDlyQ6Uc6Tcc6TSIVliIIIL3vOg+gICj7Vz2HQBGuLS5KEj/6A=="
    end

    def put_secret_key_base2(conn, _) do
        put_in conn.secret_key_base, "cyUcdAqZkvTa0K5Qsaj0vw7yU0mKCBn2GlY2qx4Yp7i2stEsO79Jzi4n6s0XchOmXKvkCTWsjaulWCxSLOq5fQ=="
    end

    @doc """
    专门用于加密验证码消息的函数，不一定会保留
    """
    def validate_encrypt(data) do
        secret = Plug.Crypto.KeyGenerator.generate(@validate_sercret_key_base, @validate_encrypted_cookie_salt)
        sign_secret = Plug.Crypto.KeyGenerator.generate(@validate_sercret_key_base, @validate_encrypted_signed_cookie_salt)

        data |> Plug.Crypto.MessageEncryptor.encrypt(secret, sign_secret)

    end

    def validate_decrypt(encrypted) do
        secret = Plug.Crypto.KeyGenerator.generate(@validate_sercret_key_base, @validate_encrypted_cookie_salt)
        sign_secret = Plug.Crypto.KeyGenerator.generate(@validate_sercret_key_base, @validate_encrypted_signed_cookie_salt)

         {:ok, msg}= encrypted |> Plug.Crypto.MessageEncryptor.decrypt(secret, sign_secret)
         msg
    end

end